一篇文章玩透awk

您所在的位置:网站首页 awk 引号 一篇文章玩透awk

一篇文章玩透awk

2024-06-16 23:18| 来源: 网络整理| 查看: 265

安装新版本gawk

awk有很多种版本,例如nawk、gawk。gawk是GNU awk,它的功能很丰富。

本教程采用的是gawk 4.2.0版本,4.2.0版本的gawk是一个比较大的改版,新支持的一些特性非常好用,而在低于4.2.0版本时这些语法可能会报错。所以,请先安装4.2.0版本或更高版本的gawk。

查看awk版本

awk --version

这里以安装gawk 4.2.0为例。

# 1.下载 wget --no-check-certificate https://mirrors.tuna.tsinghua.edu.cn/gnu/gawk/gawk-4.2.0.tar.gz # 2.解压、进入解压后目录 tar xf gawk-4.2.0.tar.gz cd gawk-4.2.0/ # 3.编译,并执行安装目录为/usr/local/gawk4.2 ./configure --prefix=/usr/local/gawk4.2 && make && make install # 4.创建一个软链接:让awk指向刚新装的gawk版本 ln -fs /usr/local/gawk4.2/bin/gawk /usr/bin/awk # 此时,调用awk将调用新版本的gawk,调用gawk将调用旧版本的gawk awk --version gawk --version

本系列的awk教程中,将大量使用到如下示例文件a.txt。

ID name gender age email phone 1 Bob male 28 [email protected] 18023394012 2 Alice female 24 [email protected] 18084925203 3 Tony male 21 [email protected] 17048792503 4 Kevin male 21 [email protected] 17023929033 5 Alex male 18 [email protected] 18185904230 6 Andy female 22 [email protected] 18923902352 7 Jerry female 25 [email protected] 18785234906 8 Peter male 20 [email protected] 17729348758 9 Steven female 23 [email protected] 15947893212 10 Bruce female 27 [email protected] 13942943905 读取文件的几种方式

读取文件有如下几种常见的方式:

按字符数量读取:每一次可以读取一个字符,或者多个字符,直到把整个文件读取完 按照分隔符进行读取:一直读取直到遇到了分隔符才停止,下次继续从分隔的位置处向后读取,直到读完整个文件 按行读取:每次读取一行,直到把整个文件读完 它是按照分隔符读取的一种特殊情况:将分隔符指定为了换行符\n 一次性读取整个文件 是按字符数量读取的特殊情况 也是按分隔符读取的特殊情况 按字节数量读取:一次读取指定数量的字节数据,直到把文件读完

下面使用Shell的read命令来演示前4种读取文件的方式(第五种按字节数读取的方式read不支持)。

按字符数量读取

read的-n选项和-N选项可以指定一次性读取多少个字符。

# 只读一个字符 read -n 1 data ,则必须使用括号包围(如print("a" > "A")),因为它是输出重定向符号。

如果省略参数,即print;等价于print $0;。

print输出数值

print在输出数据时,总是会先转换成字符串再输出。

对于数值而言,可以自定义转换成字符串的格式,例如使用sprintf()进行格式化。

print在自动转换数值(专指小数)为字符串的时候,采用预定义变量OFMT(Output format)定义的格式按照sprintf()相同的方式进行格式化。OFMT默认值为%.6g,表示有效位(整数部分加小数部分)最多为6。

$ awk 'BEGIN{print 3.12432623}' 3.12433

可以修改OFMT,来自定义数值转换为字符串时的格式:

$ awk 'BEGIN{OFMT="%.2f";print 3.99989}' 4.00 # 格式化为整数 $ awk 'BEGIN{OFMT="%d";print 3.99989}' 3 $ awk 'BEGIN{OFMT="%.0f";print 3.99989}' 4 printf printf format, item1, item2, ...

格式化字符:

修饰符:均放在格式化字符的前面

N$ N是正整数。默认情况下,printf的字段列表顺序和格式化字符 串中的%号顺序是一一对应的,使用N$可以自行指定顺序。 printf "%2$s %1$s","world","hello"输出hello world N$可以重复指定,例如"%1$s %1$s"将取两次第一个字段 宽度 指定该字段占用的字符数量,不足宽度默认使用空格填充,超出宽度将无视。 printf "%5s","ni"输出"___ni",下划线表示空格 - 表示左对齐。默认是右对齐的。 printf "%5s","ni"输出"___ni" printf "%-5s","ni"输出"ni___" 空格 针对于数值。对于正数,在其前添加一个空格,对于负数,无视 printf "% d,% d",3,-2输出"_3,-2",下划线表示空格 + 针对于数值。对于正数,在其前添加一个+号,对于负数,无视 printf "%+d,%+d",3,-2输出"+3,-2",下划线表示空格 # 可变的数值前缀。对于%o,将添加前缀0,对于%x或%X,将添加前缀0x或0X 0 只对数值有效。使用0而非默认的空格填充在左边,对于左对齐的数值无效 printf "%05d","3"输出00003 printf "%-05d","3"输出3 printf "%05s",3输出____3 ' 单引号,表示对数值加上千分位逗号,只对支持千分位表示的locale有效 $ awk "BEGIN{printf \"%'d\n\",123457890}" 123,457,890 $ LC_ALL=C awk "BEGIN{printf \"%'d\n\",123457890}" 123457890 .prec 指定精度。在不同格式化字符下,精度含义不同 %d,%i,%o,%u,%x,%X 的精度表示最大数字字符数量 %e,%E,%f,%F 的精度表示小数点后几位数 %s 的精度表示最长字符数量,printf "%.3s","foob"输出foo %g,%G 的精度表示表示最大有效位数,即整数加小数位的总数量

sprintf()

sprintf()采用和printf相同的方式格式化字符串,但是它不会输出格式化后的字符串,而是返回格式化后的字符串。所以,可以将格式化后的字符串赋值给某个变量。

awk ' BEGIN{ a = sprintf("%03d", 12.34) print a # 012 } ' 重定向输出

print[f] something | Shell_Cmd时,awk将创建一个管道,然后启动Shell命令,print[f]产生的数据放入管道,而命令将从管道中读取数据。

# 例1: awk ' NR>1{ print $2 >"name.unsort" cmd = "sort >name.sort" print $2 | cmd #print $2 | "sort >name.sort" } END{close(cmd)} ' a.txt # 例2:awk中构建Shell命令,通过管道交给shell执行 awk 'BEGIN{printf "seq 1 5" | "bash"}'

print[f] something |& Shell_Cmd时,print[f]产生的数据交给Coprocess。之后,awk再从Coprocess中取回数据。这里的|&有点类似于能够让Shell_Cmd后台异步运行的管道。

stdin、stdout、stderr

awk重定向时可以直接使用/dev/stdin、/dev/stdout和/dev/stderr。还可以直接使用某个已打开的文件描述符/dev/fd/N。

例如:

awk 'BEGIN{print "something OK" > "/dev/stdout"}' awk 'BEGIN{print "something wrong" > "/dev/stderr"}' awk 'BEGIN{print "something wrong" | "cat >&2"}' awk 'BEGIN{getline < "/dev/stdin";print $0}' $ exec 4 a.txt $ awk 'BEGIN{while((getline < "/dev/fd/4")>0){print $0}}' awk变量

awk的变量是动态变量,在使用时声明。

所以awk变量有3种状态:

未声明状态:称为untyped类型 引用过但未赋值状态:unassigned类型 已赋值状态

引用未赋值的变量,其默认初始值为空字符串或数值0。

在awk中未声明的变量称为untyped,声明了但未赋值(只要引用了就声明了)的变量其类型为unassigned。

gawk 4.2版提供了typeof()函数,可以测试变量的数据类型,包括测试变量是否声明。

awk 'BEGIN{ print(typeof(a)) # untyped if(b==0){print(typeof(b))} # unassigned }'

除了typeof(),还可以使用下面的技巧进行检测:

awk 'BEGIN{ if(a=="" && a==0){ # 未赋值时,两个都true print "untyped or unassigned" } else { print "assigned" } }' 变量赋值

awk中的变量赋值语句也可以看作是一个有返回值的表达式。

例如,a=3赋值完成后返回3,同时变量a也被设置为3。

基于这个特点,有两点用法:

可以x=y=z=5,等价于z=5 y=5 x=5 可以将赋值语句放在任意允许使用表达式的地方 x != (y = 1) awk 'BEGIN{print (a=4);print a}'

问题:a=1;arr[a+=2] = (a=a+6)是怎么赋值的,对应元素结果等于?arr[3]=7。但不要这么做,因为不同awk的赋值语句左右两边的评估顺序有可能不同。

awk中声明变量的位置

awk中使用Shell变量

要在awk中使用Shell变量,有三种方式:

1.在-v选项中将Shell变量赋值给awk变量

num=$(cat a.txt | wc -l) awk -v n=$num 'BEGIN{print n}'

-v选项是在awk工作流程的第一阶段解析的,所以-v选项声明的变量在BEGIN{}、END{}和main代码段中都能直接使用。

2.在非选项型参数位置处使用var=value格式将Shell变量赋值给awk变量

num=$(cat a.txt | wc -l) awk '{print n}' n=$num a.txt

非选项型参数设置的变量不能在BEGIN代码段中使用。

3.直接在awk代码部分暴露Shell变量,交给Shell解析进行Shell的变量替换

num=$(cat a.txt | wc -l) awk 'BEGIN{print '"$num"'}'

这种方式最灵活,但可读性最差,可能会出现大量的引号。

数据类型

gawk有两种基本的数据类型:数值和字符串。在gawk 4.2.0版本中,还支持第三种基本的数据类型:正则表达式类型。

数据是什么类型在使用它的上下文中决定:在字符串操作环境下将转换为字符串,在数值操作环境下将转换为数值。这和自然语言中的一个词语、一个单词在不同句子内的不同语义是一样的。

隐式转换:

算术加0操作可转换为数值类型 "123" + 0返回数值123 " 123abc" + 0转换为数值时为123 无效字符串将转换成0,例如"abc"+3返回3 连接空字符串可转换为字符串类型 123""转换为字符串"123" awk 'BEGIN{a="123";print typeof(a+0)}' # number awk 'BEGIN{a=123;print typeof(a"")}' # string awk 'BEGIN{a=2;b=3;print(a b)+4}' # 27

显式转换:

数值->字符串: CONVFMT或sprintf():功能等价。都是指定数值转换为字符串时的格式 awk 'BEGIN{a=123.4567;CONVFMT="%.2f";print a""}' #123.46 awk 'BEGIN{a=123.4567;print sprintf("%.2f", a)}' #123.46 awk 'BEGIN{a=123.4567;printf("%.2f",a)}' 字符串->数值:strtonum() gawk 'BEGIN{a="123.4567";print strtonum(a)}' # 123.457 awk字面量

awk中有3种字面量:字符串字面量、数值字面量和正则表达式字面量。

数值字面量 整数、浮点数、科学计数 105、105.0、1.05e+2、1050e-1 awk内部总是使用浮点数方式保存所有数值,但用户在使用可以转换成整数的数值时总会去掉小数点 数值12.0面向用户的值为12,12面向awk内部的值是12.0000000...0 # 结果是123而非123.0 awk 'BEGIN{a=123.0;print a}' 算术运算 ++ -- 自增、自减,支持i++和++i或--i或i-- ^ 幂运算(**也用于幂运算) + - 一元运算符(正负数符号) * / % 乘除取模运算 + - 加减法运算 # 注: # 1.++和--既可以当作独立语句,也可以作为表达式,如: # awk 'BEGIN{a=3;a++;a=++a;print a}' # 2.**或^幂运算是从右向左计算的:print 2**1**3得到2而不是8

赋值操作(优先级最低):

= += -= *= /= %= ^= **=

疑惑:b = 6;print b += b++输出结果?可能是12或13。不同的awk的实现在评估顺序上不同,所以不要用这种可能产生歧义的语句。

字符串字面量

awk中的字符串都以双引号包围,不能以单引号包围。

"abc" "" "\0"、"\n"

字符串连接(串联):awk没有为字符串的串联操作提供运算符,可以直接连接或使用空格连接。

awk 'BEGIN{print ("one" "two")}' # "onetwo" awk 'BEGIN{print ("one""two")}' awk 'BEGIN{a="one";b="two";print (a b)}'

注意:字符串串联虽然方便,但是要考虑串联的优先级。例如下面的:

# 下面第一个串联成功,第二个串联失败, # 因为串联优先级低于加减运算,等价于`12 (" " -23)` # 即:先转为数值0-23,再转为字符串12-23 $ awk 'BEGIN{a="one";b="two";print (12 " " 23)}' 12 23 $ awk 'BEGIN{a="one";b="two";print (12 " " -23)}' 12-23 正则表达式字面量

普通正则:

/[0-9]+/ 匹配方式:"str" ~ /pattern/或"str" !~ /pattern/ 匹配结果返回值为0(匹配失败)或1(匹配成功) 任何单独出现的/pattern/都等价于$0 ~ /pattern/ if(/pattern/)等价于if($0 ~ /pattern/) 坑1:a=/pattern/等价于将$0 ~ /pattern/的匹配返回值(0或1)赋值给a 坑2:/pattern/ ~ $1等价于$0 ~ /pattern/ ~ $1,表示用$1去匹配0或1 坑3:/pattern/作为参数传给函数时,传递的是$0~/pat/的结果0或1 坑4.坑5.坑6...

强类型的正则字面量(gawk 4.2.0才支持):

gawk支持的正则 . # 匹配任意字符,包括换行符 ^ $ [...] [^...] | + * ? () {m} {m,} {m,n} {,n} [:lower:] [:upper:] [:alpha:] [:digit:] [:alnum:] [:xdigit:] [:blank:] [:space:] [:punct:] [:graph:] [:print:] [:cntrl:] 以下是gawk支持的: \y 匹配单词左右边界部分的空字符位置 "hello world" \B 和\y相反,匹配单词内部的空字符位置,例如"crate" ~ `/c\Brat\Be/`成功 \< 匹配单词左边界 \> 匹配单词右边界 \s 匹配空白字符 \S 匹配非空白字符 \w 匹配单词组成字符(大小写字母、数字、下划线) \W 匹配非单词组成字符 \` 匹配字符串的绝对行首 "abc\ndef" \' 匹配字符串的绝对行尾

gawk不支持正则修饰符,所以无法直接指定忽略大小写的匹配。

如果想要实现忽略大小写匹配,则可以将字符串先转换为大写、小写再进行匹配。或者设置预定义变量IGNORECASE为非0值。

# 转换为小写 awk 'tolower($0) ~ /bob/{print $0}' a.txt # 设置IGNORECASE awk '/BOB/{print $0}' IGNORECASE=1 a.txt awk布尔值

在awk中,没有像其它语言一样专门提供true、false这样的关键字。

但它的布尔值逻辑非常简单:

awk ' BEGIN{ if(1){print "haha"} if("0"){print "hehe"} if(a=3){print "hoho"} # if(3){print "hoho"} if(a==3){print "aoao"} if(/root/){print "heihei"} # $0 ~ /root/ }' awk中比较操作

strnum类型

awk最基本的数据类型只有string和number(gawk 4.2.0版本之后支持正则表达式类型)。但是,对于用户输入数据(例如从文件中读取的各个字段值),它们理应属于string类型,但有时候它们看上去可能像是数值(例如$2=37),而有时候有需要这些值是数值类型。

注意,strnum类型只针对于awk中除数值常量、字符串常量、表达式计算结果外的数据。例如从文件中读取的字段$1、$2、ARGV数组中的元素等等。

$ echo "30" | awk '{print typeof($0) " " typeof($1)}' strnum strnum $ echo "+30" | awk '{print typeof($1)}' strnum $ echo "30a" | awk '{print typeof($1)}' string $ echo "30 a" | awk '{print typeof($0) " " typeof($1)}' string strnum $ echo " +30 " | awk '{print typeof($0) " " typeof($1)}' strnum strnum 大小比较操作

比较操作符:

< > = != == 大小、等值比较 in 数组成员测试

比较规则:

|STRING NUMERIC STRNUM -------|----------------------- STRING |string string string NUMERIC|string numeric numeric STRNUM |string numeric numeric

简单来说,string优先级最高,只要string类型参与比较,就都按照string的比较方式,所以可能会进行隐式的类型转换。

其它时候都采用num类型比较。

$ echo ' +3.14' | awk '{print typeof($0) " " typeof($1)}' #strnum strnum $ echo ' +3.14' | awk '{print($0 == " +3.14")}' #1 $ echo ' +3.14' | awk '{print($0 == "+3.14")}' #0 $ echo ' +3.14' | awk '{print($0 == "3.14")}' #0 $ echo ' +3.14' | awk '{print($0 == 3.14)}' #1 $ echo ' +3.14' | awk '{print($1 == 3.14)}' #1 $ echo ' +3.14' | awk '{print($1 == " +3.14")}' #0 $ echo ' +3.14' | awk '{print($1 == "+3.14")}' #1 $ echo ' +3.14' | awk '{print($1 == "3.14")}' #0 $ echo 1e2 3|awk ’{print ($1 = != == # 注意>即是大于号,也是print/printf的重定向符号 ~ !~ in && || ?: = += -= *= /= %= ^=

对于相同优先级的运算符,通常都是从左开始运算,但下面2种例外,它们都从右向左运算:

赋值运算:如= += -= *= 幂运算 a - b + c => (a - b) + c a = b = c => a =(b = c) 2**2**3 => 2**(2**3)

再者,注意print和printf中出现的>符号,这时候它表示的是重定向符号,不能再出现优先级比它低的运算符,这时可以使用括号改变优先级。例如:

awk 'BEGIN{print "foo" > a < 3 ? 2 : 1)' # 语法错误 awk 'BEGIN{print "foo" > (a < 3 ? 2 : 1)}' # 正确 流程控制语句

注:awk中语句块没有作用域,都是全局变量。

if (condition) statement [ else statement ] expr1?expr2:expr3 while (condition) statement do statement while (condition) for (expr1; expr2; expr3) statement for (var in array) statement break continue next nextfile exit [ expression ] { statements } switch (expression) { case value|regex : statement ... [ default: statement ] } 代码块 {statement} if...else # 单独的if if(cond){ statements } # if...else if(cond1){ statements1 } else { statements2 } # if...else if...else if(cond1){ statements1 } else if(cond2){ statements2 } else if(cond3){ statements3 } else{ statements4 }

搞笑题:妻子告诉程序员老公,去买一斤包子,如果看见卖西瓜的,就买两个。结果是买了两个包子回来。

# 自然语言的语义 买一斤包子 if(有西瓜){ 买两个西瓜 } # 程序员理解的语义 if(没有西瓜){ 买一斤包子 }else{ 买两个包子 } awk ' BEGIN{ mark = 999 if (mark >=0 && mark < 60) { print "学渣" } else if (mark >= 60 && mark < 90) { print "还不错" } else if (mark >= 90 && mark 60) ? "及格" : "不及格";print(b)}' awk 'BEGIN{a=50; a>60 ? b="及格" : b="不及格";print(b)}' switch...case switch (expression) { case value1|regex1 : statements1 case value2|regex2 : statements2 case value3|regex3 : statements3 ... [ default: statement ] }

awk 中的switch分支语句功能较弱,只能进行等值比较或正则匹配。

各分支结尾需使用break来终止。

{ switch($1){ case 1: print("Monday") break case 2: print("Tuesday") break case 3: print("Wednesday") break case 4: print("Thursday") break case 5: print("Friday") break case 6: print("Saturday") break case 7: print("Sunday") break default: print("What day?") break } }

分支穿透:

{ switch($1){ case 1: case 2: case 3: case 4: case 5: print("Weekday") break case 6: case 7: print("Weekend") break default: print("What day?") break } } while和do...while while(condition){ statements } do { statements } while(condition)

while先判断条件再决定是否执行statements,do...while先执行statements再判断条件决定下次是否再执行statements。

awk 'BEGIN{i=0;while(i 1 { arr[$0] = $4 } END { PROCINFO["sorted_in"] = "cmp_val_num" for (i in arr) { print i } }' a.txt

再比如,按数组元素值的字符大小来比较。

function cmp_val_str(i1,v1,i2,v2) { v1 = v1 "" v2 = v2 "" if(v1 < v2){ return -1 } else if(v1 == v2){ return 0 } else { return 1 } # return (v1 < v2) ? -1 : (v1 != v2) } NR>1{ arr[$0] = $2 } END{ PROCINFO["sorted_in"] = "cmp_val_str" for(line in arr) { print line } }

再比如,对元素值按数值升序比较,且相等时再按第一个字段ID进行数值降序比较。

awk ' function cmp_val_num(i1,v1,i2,v2, a1,a2) { if (v11{ arr[$0,$1] = $4 } END{ PROCINFO["sorted_in"] = "cmp_val_num" for(str in arr){ split(str, a, SUBSEP) print a[1] } } ' a.txt

上面使用的arr[x,y]来存储额外信息,下面使用arr[x][y]多维数组的方式来存储额外信息实现同样的排序功能。

NR>1{ arr[NR][$0] = $4 } END{ PROCINFO["sorted_in"] = "cmp_val_num" for(nr in arr){ for(line in arr[nr]){ print line } # 本文来自骏马金龙:www.junmajinlong.com } } function cmp_val_num(i1,v1,i2,v2, ii1,ii2){ # 获取v1/v2的索引,即$0的值 for(ii1 in v1){ } for(ii2 in v2){ } if(v1[ii1] < v2[ii2]){ return -1 }else if(v1[ii1] > v2[ii2]){ return 1 }else{ return (i2 - i1) } }

此外,gawk还提供了两个内置函数asort()和asorti()来对数组进行排序。

awk ARGC和ARGV

预定义变量ARGV是一个数组,包含了所有的命令行参数。该数组使用从0开始的数值作为索引。

预定义变量ARGC初始时是ARGV数组的长度,即命令行参数的数量。

ARGV数组的数量和ARGC的值只有在awk刚开始运行的时候是保证相等的。

$ awk -va=1 -F: ' BEGIN{ print ARGC; for(i in ARGV){ print "ARGV[" i "]= " ARGV[i] } }' b=3 a.txt b.txt 4 ARGV[0]= awk ARGV[1]= b=3 ARGV[2]= a.txt ARGV[3]= b.txt

awk读取文件是根据ARGC的值来进行的,有点类似于如下伪代码形式:

while(i=1;i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3